using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Text.RegularExpressions;
using System.Threading;
using System.Windows;
using System.Windows.Data;
using System.Windows.Input;
using Intuipic.Data;
using Kent.Boogaart.HelperTrinity;

namespace Intuipic
{
	/// <summary>
	/// Interaction logic for MainWindow.xaml
	/// </summary>
	public partial class MainWindow : System.Windows.Window, INotifyPropertyChanged
	{
		private readonly FileEnumerator _fileEnumerator;

		private List<string> _deleteOnExit;
		private Point _previousMousePosition;
		private readonly System.Threading.Timer _mouseTimer;
		private const int _mouseDelay = 2000;

		public static readonly DependencyProperty IsImageCyclingEnabledProperty = DependencyProperty.Register("IsImageCyclingEnabled",
			typeof(bool),
			typeof(MainWindow),
			new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));

		public static readonly DependencyProperty NavigationPercentageVisibleProperty = DependencyProperty.Register("NavigationPercentageVisible",
			typeof(double),
			typeof(MainWindow));

		public FileEnumerator FileEnumerator
		{
			get
			{
				return _fileEnumerator;
			}
		}

		public bool IsCurrentFileMarkedForDeletion
		{
			get
			{
				return _fileEnumerator.Current != null && _deleteOnExit.Contains(_fileEnumerator.Current);
			}
			set
			{
				if (_fileEnumerator.Current == null)
				{
					_navigation.IsCurrentFileMarkedForDeletion = false;
					return;
				}

				if (value && !IsCurrentFileMarkedForDeletion)
				{
					_deleteOnExit.Add(_fileEnumerator.Current);
				}
				else if (IsCurrentFileMarkedForDeletion)
				{
					_deleteOnExit.Remove(_fileEnumerator.Current);
				}

				_navigation.IsCurrentFileMarkedForDeletion = value;
				OnPropertyChanged("IsCurrentFileMarkedForDeletion");
			}
		}

		public bool IsImageCyclingEnabled
		{
			get
			{
				return (bool) GetValue(IsImageCyclingEnabledProperty);
			}
			set
			{
				SetValue(IsImageCyclingEnabledProperty, value);
			}
		}

		public double NavigationPercentageVisible
		{
			get
			{
				return (double) GetValue(NavigationPercentageVisibleProperty);
			}
			set
			{
				SetValue(NavigationPercentageVisibleProperty, value);
			}
		}

		public Settings Settings
		{
			get
			{
				return Settings.Current;
			}
		}

		public event PropertyChangedEventHandler PropertyChanged;

		public MainWindow()
		{
			_fileEnumerator = new FileEnumerator();
			Resources["NavigationOpacityConverter"] = new NavigationOpacityConverter();
			Resources["NavigationMarginConverter"] = new NavigationMarginConverter();
			_deleteOnExit = new List<string>();
			InitializeComponent();

			AssignFiles();

			Loaded += delegate
			{
				//start cycling images
				IsImageCyclingEnabled = true;
				//show first image if available
				_fileEnumerator.SelectNext();
			};

			//start a timer that hides the mouse after inactivity
			_mouseTimer = new System.Threading.Timer(HideMouse, null, _mouseDelay, Timeout.Infinite);

			//didn't hook up in XAML for some reason
			_navigation.SourceDirectoriesChanged += delegate
			{
				_navigation.Timer.Reset();
				_fileEnumerator.ClearHistory();
				AssignFiles();
				_fileEnumerator.SelectNext();
			};

			_fileEnumerator.PropertyChanged += delegate(object sender, PropertyChangedEventArgs e)
			{
				if (e.PropertyName.Equals("Current", StringComparison.OrdinalIgnoreCase))
				{
					OnPropertyChanged("IsCurrentFileMarkedForDeletion");
					_navigation.IsCurrentFileMarkedForDeletion = IsCurrentFileMarkedForDeletion;
				}
			};
		}

		private void AssignFiles()
		{
			ICollection<string> sourceDirectories = Settings.SourceDirectories;
			Regex includeExpression = Settings.IncludeExpression;
			List<string> files = new List<string>();
			List<string> filePaths = new List<string>();

			foreach (string sourceDirectory in sourceDirectories)
			{
				Debug.WriteLine(string.Format("Checking directory '{0}'.", sourceDirectory));
				filePaths.Clear();
				GetFilePathsRecursive(sourceDirectory, filePaths);

				foreach (string filePath in filePaths)
				{
					if (includeExpression.IsMatch(System.IO.Path.GetFileName(filePath)))
					{
						files.Add(filePath);
					}
				}
			}

			_fileEnumerator.SetFiles(files);
		}

		private void GetFilePathsRecursive(string sourceDirectory, List<string> filePaths)
		{
			if (Directory.Exists(sourceDirectory))
			{
				try
				{
					filePaths.AddRange(Directory.GetFiles(sourceDirectory));

					foreach (string directory in Directory.GetDirectories(sourceDirectory))
					{
						GetFilePathsRecursive(directory, filePaths);
					}
				}
				catch (UnauthorizedAccessException)
				{
					//ignore
				}
			}
		}

		private void Window_MouseMove(object sender, MouseEventArgs e)
		{
			//the MouseMove event is raised sometimes even when the mouse isn't moved. I think it's a WPF bug as it seems to happen when the image
			//changes. To counteract this, we just remember the last mouse position and only act if it has changed since
			Point position = Mouse.GetPosition(this);

			if (position != _previousMousePosition)
			{
				_previousMousePosition = position;
				//mouse was moved - reset the timer that hides the mouse cursor (don't fire timer if mouse is over navigation because it shouldn't
				//be hidden)
				_mouseTimer.Change(_navigation.IsMouseOver ? Timeout.Infinite : _mouseDelay, Timeout.Infinite);
				Cursor = Cursors.Arrow;
			}
		}

		private delegate void HideMouseHandler(object state);

		private void HideMouse(object state)
		{
			if (!Dispatcher.CheckAccess())
			{
				Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Send, new HideMouseHandler(HideMouse), state);
			}
			else
			{
				Cursor = Cursors.None;
			}
		}

		private void ChooseFolders(object sender, EventArgs e)
		{
			_navigation.IsFolderSelectionOpen = !_navigation.IsFolderSelectionOpen;
		}

		private void CycleDisplayOrder(object sender, EventArgs e)
		{
			if (Settings.DisplayOrder == DisplayOrder.Random)
			{
				Settings.DisplayOrder = DisplayOrder.Descending;
			}
			else
			{
				++Settings.DisplayOrder;
			}
		}

		private void Information(object sender, EventArgs e)
		{
			Information information = new Information();
			information.Owner = this;
			information.ShowDialog();
		}

		private void Previous_CanExecute(object sender, CanExecuteRoutedEventArgs e)
		{
			e.CanExecute = FileEnumerator.HasFiles;
		}

		private void Previous(object sender, EventArgs e)
		{
			_navigation.Timer.Reset();
			_fileEnumerator.SelectPrevious();
		}

		private void Next_CanExecute(object sender, CanExecuteRoutedEventArgs e)
		{
			e.CanExecute = FileEnumerator.HasFiles;
		}

		private void Next(object sender, RoutedEventArgs e)
		{
			_navigation.Timer.Reset();
			_fileEnumerator.SelectNext();
		}

		private void PausePlay_CanExecute(object sender, CanExecuteRoutedEventArgs e)
		{
			e.CanExecute = FileEnumerator.HasFiles;
		}

		private void PausePlay(object sender, EventArgs e)
		{
			IsImageCyclingEnabled = !IsImageCyclingEnabled;
		}

		private void IncreaseDelay(object sender, EventArgs e)
		{
			Settings.Delay += new TimeSpan(0, 0, 0, 0, (int) (_navigation.SmallChange * 1000));

			if (Settings.Delay.TotalSeconds > _navigation.MaximumChange)
			{
				Settings.Delay = new TimeSpan(0, 0, 0, 0, (int) (_navigation.MaximumChange * 1000));
			}
		}

		private void DecreaseDelay(object sender, EventArgs e)
		{
			Settings.Delay -= new TimeSpan(0, 0, 0, 0, (int) (_navigation.SmallChange * 1000));

			if (Settings.Delay.TotalSeconds < _navigation.MinimumChange)
			{
				Settings.Delay = new TimeSpan(0, 0, 0, 0, (int) (_navigation.MinimumChange * 1000));
			}
		}

		private void MarkAsDeleted_CanExecute(object sender, CanExecuteRoutedEventArgs e)
		{
			e.CanExecute = FileEnumerator.HasFiles;
		}

		private void MarkAsDeleted(object sender, EventArgs e)
		{
			IsCurrentFileMarkedForDeletion = !IsCurrentFileMarkedForDeletion;
		}

		private void _openFoldersHyperlink_Click(object sender, EventArgs e)
		{
			_navigation.IsFolderSelectionOpen = true;
		}

		private void Minimize(object sender, EventArgs e)
		{
			WindowState = WindowState.Minimized;
		}

		private void Close(object sender, EventArgs e)
		{
			Close();
		}

		private void Window_Closing(object sender, CancelEventArgs e)
		{
			if (_deleteOnExit.Count > 0)
			{
				bool cyclingEnabled = IsImageCyclingEnabled;
				//confirm the deletion of files
				IsImageCyclingEnabled = false;

				List<FileInfo> deleteOnExitFileInfos = _deleteOnExit.ConvertAll<FileInfo>(delegate(string path)
				{
					return new FileInfo(path);
				});

				DeletionWindow deletionWindow = new DeletionWindow(deleteOnExitFileInfos);
				deletionWindow.Owner = this;
				deletionWindow.ShowDialog();

				if (!deletionWindow.DialogResult.Value)
				{
					//user wants to cancel the close
					e.Cancel = true;
					IsImageCyclingEnabled = cyclingEnabled;
					return;
				}
			}
		}

		private void Window_Closed(object sender, EventArgs e)
		{
			Settings.Save();
		}

		private void OnPropertyChanged(string propertyName)
		{
			Debug.Assert(propertyName != null);
			EventHelper.Raise(PropertyChanged, this, new PropertyChangedEventArgs(propertyName));
		}

		#region Converters

		//figures out the opacity of the navigation
		private sealed class NavigationOpacityConverter : IMultiValueConverter
		{
			public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
			{
				bool pinned = (bool) values[0];
				bool folderSelectionVisible = (bool) values[1];

				if (!pinned || folderSelectionVisible)
				{
					return 1D;
				}
				else
				{
					double percent = (double) values[2];
					return Math.Max(percent, 0.4);
				}
			}

			public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
			{
				return null;
			}
		}

		//figures out the margin on the navigation
		private sealed class NavigationMarginConverter : IMultiValueConverter
		{
			public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
			{
				bool pinned = (bool) values[0];
				bool folderSelectionVisible = (bool) values[1];

				if (pinned || folderSelectionVisible)
				{
					//no margin when pinned
					return new Thickness(0);
				}
				else
				{
					//when unpinned, margin depends on current percentage of navigation currently visible (which is animated)
					double visiblePercentage = (double) values[2];
					double actualHeight = (double) values[3];
					return new Thickness(0, -1 * actualHeight + (actualHeight * visiblePercentage), 0, 0);
				}
			}

			public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
			{
				return null;
			}
		}

		#endregion
	}
}